<?php
/*
 * Copyright (c) 2008 Sergey Bondari, bondari@bondari.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Mosaic\MCGI;

const SCGI = '1';
const MCGI = '2';


/**
 * MCGI interface packet representation
 *
 * @package Library
 * @subpackage MCGI
 */
class Packet {
	/**
	 * Protocol Headers
	 * @var array
	 */
	private $headers;

	/**
	 * Protocol body
	 * @var string
	 */
	private $body;

	/**
	 * SCGI response header value
	 * @var string
	 */
	private $scgiContentType;

	/**
	 * Constructor
	 * Initializes packet with initial data. Default is MCGI protocol version
	 */
	function __construct($proto = MCGI) {
		$this->setHeader('SCGI' , $proto);
		$this->setBody('');
		$this->setResponseStatus(200, 'OK');
		$this->scgiContentType = 'text/html';

	}

	/**
	 * Return a header value
	 * @param integer HTTP status code
	 * @param string Status message
	 * @return void
	 */
	public function setResponseStatus($status, $message) {
		$this->setHeader('STATUS', $status.' '.$message);
	}
	/**
	 * Returns current protocol version
	 * @return SCGI|MCGI
	 */
	public function protocol() {
		return $this->headers['SCGI'];
	}


	/**
	 * Sets current protocol version
	 * @param string SCGI|MCGI
	 * @return void
	 */
	public function setProtocol($val) {
		$this->headers['SCGI'] = $val;
	}

	/**
	 * Return a header value
	 * @param string Header name
	 * @return string
	 */
	public function getHeader($key) {
		return isset($this->headers[$key])?$this->headers[$key]:NULL;
	}

	/**
	 * Return all headers array
	 * @return array
	 */
	public function getHeaders() {
		return $this->headers;
	}

	/**
	 * Sets header value
	 * @param string Header name
	 * @param string Header value
	 * @return void
	 */
	public function setHeader($key,$val) {
		$this->headers[$key] = $val;
	}

	/**
	 * Sets protocol body
	 * @param string
	 * @return void
	 */
	public function setBody($val) {
		$this->body = $val;
		$this->setHeader('CONTENT_LENGTH', strlen($val));
	}

	/**
	 * Gets protocol body
	 * @return string
	 */
	public function getBody() {
		return $this->body;
	}


	/**
	 * Generates SCGI response headers
	 * @return string Headers according to HTTP specification
	 */
	private function getSCGIResponseHeaders() {
		$_headers = array();
		foreach ($this->headers as $k => $v) {
			/*
			 * Exclude MCGI protocol headers
			 */
			if (!in_array($k, array('STATUS', 'SCGI', 'CONTENT_LENGTH'))) $_headers[] = $k.': '.$v;
		}

		return  'Status: '.$this->headers['STATUS']."\r\n" . 
			'Content-type: '.$this->scgiContentType."\r\n" .
			implode("\r\n", $_headers) .
			"\r\n".(count($_headers)?"\r\n":"");
	}


	/**
	 * Generates SCGI request headers
	 * @return string Headers according to SCGI specification
	 */
	private function getSCGIRequestHeaders() {
		$_headers = '';
		foreach ($this->headers as $k => $v) {
			$_headers .= $k."\0";
			$_headers .= $v."\0";
		}

		return strlen($_headers).':'.$_headers.',';
	}


	/**
	 * Redefines default SCGI/1 response content type (text/html)
	 * @param string $value MIME content type
	 * @return void
	 */
	public function setContentType($value) {
		$this->scgiContentType = $value;
	}


	/**
	 * Receives incoming request
	 * @param resource Stream socket resource
	 * @return void
	 */
	public function receive($stream) {
		/*
		 * Read headers length - /[0-9]{1,10}:/
		 */
		$hLen = stream_get_line($stream, 11, ':');

		if (preg_match('/\D/', $hLen) || !$hLen)
			throw new \Mosaic\System\IOException('Cannot recognize SCGI/MCGI protocol.');

		/*
		 * Read headers vector
		 */
		$_headers = explode("\0", fread($stream, (int)$hLen));


		/*
		 * Read netstring "," symbol after headers
		 */
		fread($stream, 1);


		$this->headers = array();

		/*
		 * Make a headers map from headers vector
		 */
		while (count($_headers) >= 2) {
			$this->headers[array_shift($_headers)] = array_shift($_headers);
		}

		/*
		 * Verify protocol version. 
		 * SCGI has 1 in the SCGI header value
		 * Mosaic MSGI protocol will have 2
		 */
		if (!isset($this->headers['SCGI']) || ($this->headers['SCGI'] != SCGI && $this->headers['SCGI'] != MCGI)) {
			throw new \Mosaic\System\IOException("Request is not MSGI/SCGI/1 compliant");
		}

		/*
		 * Check for required CONTENT_LENGTH header of MCGI/SCGI protocol.
		 */
		if (!isset($this->headers['CONTENT_LENGTH']))
			throw new \Mosaic\System\IOException("CONTENT_LENGTH header is missing");

		/*
		 * Read request content if applicable
		 */
		$this->body = ($this->headers['CONTENT_LENGTH'] > 0 ? \Mosaic\System\streamRead($stream, (int) $this->headers['CONTENT_LENGTH']) : null);
	}



	/**
	 * Sends response
	 * @param resource Stream socket resource
	 * @return void
	 */
	public function send($stream) {
		switch ($this->protocol()) {
			case SCGI:
				$headers = $this->getSCGIResponseHeaders();
			break;
			case MCGI:
				$headers = $this->getSCGIRequestHeaders();
			break;
		}

		$data = $headers . $this->body;
		$_ = \Mosaic\System\streamWrite($stream, $data);

		if ($_ === false  || $_ < ($this->headers['CONTENT_LENGTH'] + strlen($headers))) 
			throw new \Mosaic\System\IOException('Could not send all data to socket');
	}



}

